studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
232 lines (178 loc) • 5.97 kB
text/typescript
import { apiResponseLogger } from 'studiocms:logger';
import { Notifications } from 'studiocms:notifier';
import { SDKCore } from 'studiocms:sdk';
import type { tsPageContentSelect, tsPageDataSelect } from 'studiocms:sdk/types';
import {
AllResponse,
createEffectAPIRoutes,
createJsonResponse,
Effect,
genLogger,
OptionsResponse,
readAPIContextJson,
} from '../../../../../../effect.js';
import { verifyAuthTokenFromHeader } from '../../../utils/auth-token.js';
type UpdatePageData = Partial<tsPageDataSelect>;
type UpdatePageContent = Partial<tsPageContentSelect>;
interface UpdatePageJson {
pageId: string;
data?: UpdatePageData;
content?: UpdatePageContent;
}
export const { GET, PATCH, DELETE, OPTIONS, ALL } = createEffectAPIRoutes(
{
GET: (ctx) =>
genLogger('studioCMS:rest:v1:pages:[id]:GET')(function* () {
const [sdk, user] = yield* Effect.all([SDKCore, verifyAuthTokenFromHeader(ctx)]);
if (user instanceof Response) {
return user;
}
const { rank } = user;
if (rank !== 'owner' && rank !== 'admin' && rank !== 'editor') {
return apiResponseLogger(401, 'Unauthorized');
}
const { id } = ctx.params;
if (!id) {
return apiResponseLogger(400, 'Invalid page ID');
}
const page = yield* sdk.GET.page.byId(id);
if (!page) {
return apiResponseLogger(404, 'Page not found');
}
return createJsonResponse(page);
}),
PATCH: (ctx) =>
genLogger('studioCMS:rest:v1:pages:[id]:PATCH')(function* () {
const [sdk, user, notifier] = yield* Effect.all([
SDKCore,
verifyAuthTokenFromHeader(ctx),
Notifications,
]);
if (user instanceof Response) {
return user;
}
const { rank, userId } = user;
if (rank !== 'owner' && rank !== 'admin' && rank !== 'editor') {
return apiResponseLogger(401, 'Unauthorized');
}
const { id } = ctx.params;
if (!id) {
return apiResponseLogger(400, 'Invalid page ID');
}
const jsonData = yield* readAPIContextJson<UpdatePageJson>(ctx);
const { data, content } = jsonData;
if (!data) {
return apiResponseLogger(400, 'Invalid form data, data is required');
}
if (!content) {
return apiResponseLogger(400, 'Invalid form data, content is required');
}
if (!data.id) {
return apiResponseLogger(400, 'Invalid form data, id is required');
}
if (!content.id) {
return apiResponseLogger(400, 'Invalid form data, id is required');
}
if (data.id !== id) {
return apiResponseLogger(400, 'Payload id does not match path id');
}
const currentPageData = yield* sdk.GET.page.byId(id);
if (!currentPageData) {
return apiResponseLogger(404, 'Page not found');
}
const { authorId, contributorIds, defaultContent } = currentPageData.data;
let AuthorId = authorId;
if (!authorId) {
AuthorId = userId || null;
}
const ContributorIds = contributorIds || [];
if (!ContributorIds.includes(userId)) {
ContributorIds.push(userId);
}
data.authorId = AuthorId;
data.contributorIds = ContributorIds;
data.updatedAt = new Date();
const startMetaData = (yield* sdk.GET.databaseTable.pageData()).find(
(metaData) => metaData.id === data.id
);
yield* sdk.UPDATE.page.byId(data.id, {
pageData: data as tsPageDataSelect,
pageContent: content as tsPageContentSelect,
});
const updatedMetaData = (yield* sdk.GET.databaseTable.pageData()).find(
(metaData) => metaData.id === data.id
);
const siteConfig = yield* sdk.GET.siteConfig();
if (!siteConfig) {
return apiResponseLogger(500, 'Site configuration not found');
}
const { enableDiffs, diffPerPage = 10 } = siteConfig.data;
if (enableDiffs) {
yield* sdk.diffTracking.insert(
userId,
data.id,
{
content: {
start: defaultContent?.content || '',
end: content.content || '',
},
// biome-ignore lint/style/noNonNullAssertion: This is a valid use case for non-null assertion
metaData: { start: startMetaData!, end: updatedMetaData! },
},
diffPerPage
);
}
yield* sdk.CLEAR.page.byId(id);
// biome-ignore lint/style/noNonNullAssertion: This is a valid use case for non-null assertion
yield* notifier.sendEditorNotification('page_updated', updatedMetaData!.title);
return apiResponseLogger(200, 'Page updated successfully');
}).pipe(Notifications.Provide),
DELETE: (ctx) =>
genLogger('studioCMS:rest:v1:pages:[id]:DELETE')(function* () {
const [sdk, user, notifier] = yield* Effect.all([
SDKCore,
verifyAuthTokenFromHeader(ctx),
Notifications,
]);
if (user instanceof Response) {
return user;
}
const { rank } = user;
if (rank !== 'owner' && rank !== 'admin') {
return apiResponseLogger(401, 'Unauthorized');
}
const { id } = ctx.params;
if (!id) {
return apiResponseLogger(400, 'Invalid page ID');
}
const jsonData = yield* readAPIContextJson<{ slug: string }>(ctx);
const { slug } = jsonData;
if (!slug) {
return apiResponseLogger(400, 'Invalid request');
}
const page = yield* sdk.GET.page.byId(id);
if (!page) {
return apiResponseLogger(404, 'Page not found');
}
yield* sdk.DELETE.page(id);
yield* sdk.CLEAR.page.byId(id);
yield* notifier.sendEditorNotification('page_deleted', page.data.title);
return apiResponseLogger(200, 'Page deleted successfully');
}).pipe(Notifications.Provide),
OPTIONS: () =>
Effect.try(() => OptionsResponse({ allowedMethods: ['GET', 'PATCH', 'DELETE'] })),
ALL: () => Effect.try(() => AllResponse()),
},
{
cors: { methods: ['GET', 'PATCH', 'DELETE', 'OPTIONS'] },
onError: (error) => {
console.error('API Error:', error);
return createJsonResponse(
{ error: 'Internal Server Error' },
{
status: 500,
}
);
},
}
);